#!/usr/bin/env python

from bitsio import BitsIO
import json

NMEA_DICT = json.load(open("include/nmea2k.json"))

class N2KDB:
    '''Maintains a database of received N2K messages.

    The reason for this is mainly because of fast packet messages, so
    that multiple received strs can be combined into one message. Also
    should be useful for dbus requests for current or previous
    messages received.'''

    def __init__(self,depth=0):
        self.depth = depth
        self.data = {}

    def addtodb(self, m):
        self.data[m.pgn] = m

    def received_str(self, s):
        '''Process s, is a hex ascii'''
        try:
            ba = bytearray.fromhex(unicode(s))
        except(ValueError):
            return('Error converting to hex')

        bio = BitsIO(ba)
        return(self.received_bitsio(bio))

    def received_bitsio(self, bio):
        if(bio.bitlength() < DATA_START_POS):
            return('Message length: {0}'.format(bio.bytelength()))
        h = header(bio)
        pgn = h['pgn']
        try:
            m = self.data[pgn]
        except:
            m = N2KMessage(pgn)
            self.addtodb(m)
        m.process_bitsio(bio,head=h)
        return(m)

class N2KMessage:
    '''The message format, which is passed around for N2K.'''

    def __init__(self,pgn):
        self.pgn = pgn
        self.bio = BitsIO()
        self.fast_length = 0

    def process_bitsio(self, bio,**kwargs):
        '''Process BitIO object into header and fields

        Fast packets will be concatenated here'''
        if 'head' in kwargs:
            self.h = kwargs['head']
        else:
            self.h = header(bio)
        
        if isfastpacket(self.h['pgn'],bio):
            i,l = fast_index_and_length(bio)
            if l:
                self.fast_length = l
                self.bio.extend(bio[0:DATA_START_POS])
                pos_st = DATA_START_POS+16
            else:
                pos_st = DATA_START_POS+8
                
            self.bio.extend(bio[pos_st:])
#            print('fast packet')
            if(self.bio.bytelength()-DATA_START_POS/8 >= 
               self.fast_length):
                self.f = fields(self.h['pgn'],self.bio)
            else:
                self.f = 'unfinished fast packet'
        else:
            self.bio = bio
            self.f = fields(self.h['pgn'],self.bio)
        
        return(self.h,self.f)

    def getdict(self):
        d = {}
        d['pgn'] = self.h['pgn']
        d['fields'] = self.f
        d['header'] = self.h
        d['raw'] = hex(self.bio)
        return(d)
    


# def process_str(s):
#     '''Process s, is a hex ascii'''
#     bio = BitsIO(bytearray.fromhex(unicode(s)))
#     return(process_bitsio(bio))

# def process_bitsio(bio):
#     '''Process BitIO object into header and fields'''
#     h = header(bio)
#     f = fields(h['pgn'],bio)
#     return(h,f)


def parse_fields(fmt,bio):
    l = bio.readlist(fmt)
    return(l)


HEADER_START_POS = 8
DATA_START_POS = 5*8

def header(bio):
    bio.seek(HEADER_START_POS)
    f = parse_fields([3,1,1,6,2,2,8,8,1],bio)
    header_names = ['priority','edp','dp','pf_upper','ide','pf_lower',
                    'ps','sa','rtr']
    c = dict(zip(header_names,f))
    c['raw'] = hex(bio[HEADER_START_POS:DATA_START_POS])
    c['pf'] = (c['pf_upper']<<2) + c['pf_lower']
    if (c['pf'] >= 240):
        c['pgn'] = (c['dp']<<16) + (c['pf']<<8) + c['ps']
    else:
        c['pgn'] = (c['dp']<<16) + (c['pf']<<8)
    return(c)


def fields(pgn,bio):
    bio.seek(DATA_START_POS)
    info = NMEA_DICT.get(str(pgn),None)
    if info:
        fmtlist = info['fields']
        f = parse_fields(fmtlist,bio)
        return(fieldlist(fmtlist,f))
    else:
        return('not in dictionary')

def fieldlist(fmtlist,fdata):
    d = {}
    for item,data in zip(fmtlist,fdata):
        if isinstance(item,list):
            print(data)
            d['repeat1'] = fieldlist(item,data)
        else:
            res = item['resolution']
            units = item['units']
            if (item['bitlength'] > 7): # note that this may be
                # temporary
                if ((item['signed'] and 
                     data == 2**(item['bitlength']-1)-1) or
                    (not item['signed'] and 
                     data == 2**item['bitlength']-1)):
                    data = None

            if data:
                if isinstance(res,float):
                    data = data*res
                elif isinstance(units,dict):
                    units = units.get(str(data),None)

            d[item['name']] = data,units
    return(d)


def isfastpacket(pgn,bio):
    info = NMEA_DICT.get(str(pgn),None)
    #return(info['fastpacket'])
    if info:
        fbl = sum(x['bitlength'] if isinstance(x,dict) else 0 
                  for x in info['fields'])
        return(fbl > 64)
    else:
        return(False)


def fast_index_and_length(bio):
    bio.seek(DATA_START_POS)
    i,l = parse_fields([8,8],bio)
    if (i & 0xF == 0):          # start of the packet
        return(i,l)
    else:
        return(i,None)
